feat: per-thread serialization, conditional streaming, and multi-bot fixes#420
Conversation
Review Discussion: Conditional Streaming vs Blanket Send-OnceContextThis PR switches all response delivery to send-once (emoji progress indicators + single message at completion), removing streaming edit entirely. This cleanly fixes bot-to-bot communication, but it also regresses the most common scenario: one human + one bot. Line of Reasoning1. The core problems this PR fixes are specifically two:
Both share the same root cause: no message events should appear on Slack before the reply is complete. 2. In Mostly no — each bot answers its own question and doesn't mention the other. An LLM could theoretically add an @mention on its own, but this is rare in practice and can be mitigated via system prompt. 3. What about All bot messages are dropped, including 4. Conclusion: most users don't need to pay the UX cost for a bot-to-bot feature they haven't enabled. Suggestion: Conditional StreamingBranch behavior based on the Benefits:
This is not a blocker — the blanket send-once in this PR is correct and safe. This is a suggestion for a follow-up optimization so the default experience remains unchanged. |
…onfig When allow_bot_messages=off (default): use streaming edit (placeholder + live updates via chat.update) for better human UX. When allow_bot_messages=mentions|all: use send-once to protect bot-to-bot communication from placeholder/message_changed interference. Addresses: #420 (comment)
Follow-up commits pushed: conditional streaming + multi-bot fixesPushed 3 commits addressing the conditional streaming suggestion and two bugs found during multi-bot testing. 1.
|
allow_bot_messages |
Delivery | Why |
|---|---|---|
off (default) |
Streaming edit | Bot messages are dropped anyway — majority of users keep the live-update UX |
mentions / all |
Send-once | Protects bot-to-bot from "…" placeholder triggering responses and message_changed being filtered by skip_subtype |
Changes:
- Added
edit_message()anduse_streaming()toChatAdaptertrait (default: no-op / false) SlackAdapterimplementsedit_messageviachat.update, returnsuse_streaming() = truewhenallow_bot_messages == Offstream_prompt()branches: streaming mode sends"…"placeholder + 1.5s edit loop; send-once mode accumulates and sends at completion- Added
format::truncate_chars()for streaming display truncation
2. d814158 — fix(discord): remove mention_roles from is_mentioned check
Bug: when multiple bots share a Discord channel, @mentioning one bot triggered the other. Root cause: the is_mentioned check included mention_roles — if both bots share a role, the role-based match fired for both.
Fix: removed the mention_roles clause. mentions_user_id() + raw content <@BOT_ID> check is sufficient and correctly scoped to the specific bot being mentioned.
3. 1260507 — fix(helm): add allowUserMessages for Discord in configmap
Bug: allowUserMessages was only rendered in the [slack] section of the Helm configmap template. Setting agents.kiro.discord.allowUserMessages=mentions via Helm was silently ignored — Discord always defaulted to involved.
Fix: added the same allowUserMessages block to the [discord] section with validation (involved / mentions).
Testing
Validated on local OrbStack cluster with two Kiro-powered bots (AgentBroker + AgentDealer) in the same Discord channel:
- ✅
@AgentBroker→ only AgentBroker responds - ✅
@AgentDealer→ only AgentDealer responds - ✅ Slack streaming edit works when
allow_bot_messages = "off" - ✅ Slack send-once works when
allow_bot_messages = "mentions" - ✅
allowUserMessages = "mentions"correctly requires @mention in Discord threads
No breaking changes — default behavior is unchanged.
變更摘要(繁體中文)起因處理 PR #420 的 review comment — 建議 Slack 的回覆方式不要一刀切改成 send-once,應該根據 完成的事項1. Slack 條件式串流
2. Discord 多 bot 修正
3. Helm chart 修正
4. 部署 AgentDealer(第二個 bot)驗證
影響零 breaking change,預設行為不變。 架構圖 |
|
Related issues (POC validation for tonight's fixes): |
dc87eb9 to
0026143
Compare
…d in SenderContext
…onfig When allow_bot_messages=off (default): use streaming edit (placeholder + live updates via chat.update) for better human UX. When allow_bot_messages=mentions|all: use send-once to protect bot-to-bot communication from placeholder/message_changed interference. Addresses: openabdev#420 (comment)
The mention_roles check caused false positives when multiple bots share a channel — @mentioning one bot would trigger the other if they share a Discord role. The mentions_user_id + content check is sufficient.
…gmap Previously allowUserMessages was only rendered for the [slack] section. This adds the same support for [discord], enabling per-agent control of whether the bot requires @mention in threads.
0026143 to
61f9b96
Compare
…ority truncation - KeyedAsyncQueue::acquire() now does lazy cleanup when entries > 100, evicting idle semaphores (strong_count == 1 && permits available) - Replaced expect() with match + warn + skip (returns Option) to avoid task panic on closed semaphore - Removed unused cleanup_idle() (dead code) - Switched streaming truncation from head-priority to tail-priority so users see the most recent agent output during live updates - Added comments explaining queue_key construction differences between app_mention (ts-only) and message (channel:ts for DM uniqueness)
Note: Role mentions no longer trigger the botCommit This is the correct behavior:
No code change needed — just documenting the intentional behavior change. |
Fixes #419
Fixes #440
Fixes #441
What problem does this solve?
Two bugs that break Slack bot-to-bot communication, plus one UX bug where agents reply to channel root instead of thread.
See issue #419 for full root cause analysis.
Prior Art Research
OpenClaw
chat.updateevery 1s (or nativechat.startStream)allowBots: falseby default — drops all bot messages, no partial-content fixHermes Agent
GatewayStreamConsumer— send initial +chat.updateloop, ~1s intervalSLACK_ALLOW_BOTS=noneby default — drops all bot messages_active_sessionsdict for some serialization, but blockingKey difference: both frameworks avoid bot-to-bot by dropping all bot messages. OpenAB supports configurable bot-to-bot (
allow_bot_messages), which requires this fix to work correctly.What's in this PR
src/adapter.rsedit_message()+use_streaming()onChatAdaptertrait (default: no-op / false);stream_prompt()branches based on adapter configsrc/slack.rsSlackAdapterstoresallow_bot_messages, implementsedit_messageviachat.update, returnsuse_streaming()=truewhenOffsrc/slack.rsKeyedAsyncQueuewith lazy cleanup + panic-safe acquire; per-thread serialization for bothapp_mentionandmessagesrc/discord.rsmention_rolesfromis_mentioned(fixes multi-bot false positive)charts/openab/templates/configmap.yamlallowUserMessagessupport for[discord]section (parity with[slack])src/format.rstruncate_chars_tail()— tail-priority truncation for streaming displayBehavior change
Conditional streaming (Slack)
allow_bot_messagesoff(default)"…"→ live updates → final)mentions/allmessage_changedinterferenceBot-to-bot (send-once path)
Human UX (streaming path, default)
Multi-bot Discord fix
thread_id in SenderContext
{ "schema": "openab.sender.v1", "channel": "slack", "channel_id": "C0123456789", "thread_id": "1234567890.123456", "sender_id": "U0123456789" }Test plan
cargo check— clean, no warningscargo clippy— cleanallow_bot_messages = "off"allow_bot_messages = "mentions"@AgentBroker→ only AgentBroker responds@AgentDealer→ only AgentDealer respondsallowUserMessages = "mentions"correctly requires @mention in threadsagents.X.discord.allowUserMessagesrenders in configmapsender_context.thread_idDiscord thread
https://discord.com/channels/1491295327620169908/1493496229168939130/1494352889563320430